import cv2
from keys import *
from handTracker import *
from pynput.keyboard import Controller
import time

# 虚拟键盘类
class VirtualKb():

    def __init__(self, startX = 40, startY = 200, key_w = 80, key_h =60, show = True):
        self.startX = startX  
        self.startY = startY
        self.key_w = key_w 
        self.key_h = key_h
        self.show = show
        self.keys = self.getKeysDic()
        self.clickedX, self.clickedY = 0, 0
        self.mouseX, self.mouseY = 0, 0
        self.thumbTipX, self.thumbTipY = 0, 0
        self.signTipX, self.signTipY = 0, 0
        # initiating the hand tracker
        self.tracker = HandTracker(detectionCon=0.8)
        self.keyboard = Controller()
        self.chickTime = 0  # 用来防止 手指打字一直打
    
    # 获得鼠标位置 函数
    def getMousPos(self, event , x, y, flags, param):        
        # 如果左键释放 意味着点击结束 就记录鼠标 x,y位置
        if event == cv2.EVENT_LBUTTONUP:
            self.clickedX, self.clickedY = x, y
        # 如果鼠标移动就更新鼠标当前x,y位置
        if event == cv2.EVENT_MOUSEMOVE:
            self.mouseX, self.mouseY = x, y


    def addKeyboard(self, frame):
        # 使用show来控制虚拟键盘的显示与隐藏
        if self.show:
            self.keys['textBox'].drawKey(frame, (255,255,255), (0,0,0), 0.3)
            for key in self.keys.values():
                if key.isOver(self.mouseX, self.mouseY) or key.isOver(self.signTipX, self.signTipY):
                    # 光标移到某个按键就高亮显示
                    key.drawKey(frame,(255,255,255), (0,0,0), alpha = 0.1)

                # writing using mouse left click
                # if key.isOver(self.clickedX, self.clickedY) or key.isOver(self.thumbTipX, self.thumbTipY):                              
                #     if key.text == 'Backspace':
                #         self.keys['textBox'].text = self.keys['textBox'].text[:-1]
                #     elif key.text == 'clr':
                #         self.keys['textBox'].text = ''
                #     elif len(self.keys['textBox'].text) < 30:
                #         if key.text == 'Space':
                #             self.keys['textBox'].text += " "
                #         else:
                #             self.keys['textBox'].text += key.text
                #     # 重新 调回点击位置 防止一直写情况
                #     self.clickedX, self.clickedY = 0, 0
                
                # writing using fingers
                if key.isOver(self.thumbTipX, self.thumbTipY):
                    currentTime = time.time()
                    if currentTime - self.chickTime > 1:
                        if key.text == 'Backspace':
                            self.keys['textBox'].text = self.keys['textBox'].text[:-1]
                        elif key.text == 'clr':
                            self.keys['textBox'].text = ''
                        elif len(self.keys['textBox'].text) < 30:
                            if key.text == 'Space':
                                self.keys['textBox'].text += " "
                            else:
                                self.keys['textBox'].text += key.text
                                #simulating the press of actuall keyboard
                                self.keyboard.press(key.text)
                        
                        self.thumbTipX, self.thumbTipY = 0, 0
                        self.chickTime = currentTime
                # 未被光标移到的按键就正常显示
                key.drawKey(frame,(255,255,255), (0,0,0), alpha = 0.5 )
                

    def getKeysDic(self):
        keys={}          # 这个会被返回    
        letters =list("QWERTYUIOPASDFGHJKLZXCVBNM")   # 26个键

        # 输入框
        keys['textBox'] = Key(self.startX, self.startY-self.key_h-5, 10*self.key_w+9*5, self.key_h, '')

        # 把各个按键放置在相应位置
        for i,l in enumerate(letters):
            # 应该参考实际键盘位置
            if i<10:
                keys[l] = Key(self.startX + i*self.key_w + i*5, self.startY, self.key_w, self.key_h, l)
            elif i<19:
                keys[l] = Key(self.startX + (i-10)*self.key_w + i*5, self.startY + self.key_h + 5, self.key_w, self.key_h, l)
            else:
                keys[l] = Key(self.startX + (i-19)*self.key_w + i*5, self.startY + 2*self.key_h + 10, self.key_w, self.key_h, l)

        # 在相应位置放上空格、清空、回退键
        keys['Space'] = Key(self.startX+25, self.startY+3*self.key_h+15, 5*self.key_w, self.key_h, "Space")
        keys['clr'] = Key(self.startX+8*self.key_w + 50, self.startY+2*self.key_h+10, self.key_w, self.key_h, "clr")
        keys['Backspace'] = Key(self.startX+5*self.key_w+30, self.startY+3*self.key_h+15, 5*self.key_w, self.key_h, "Backspace")

        # 返回26个键以及一些功能键
        return keys

    # 用来控制显示/隐藏 以及 退出
    def controller(self, frame, w):
        # 在相应位置放上一些控制键盘的功能键
        showKey = Key(300, 5, 80, 50, 'Show')
        exitKey = Key(300, 65, 80, 50, 'Exit')
        showKey.x = int(w*1.5) - 85
        exitKey.x = int(w*1.5) - 85
        showKey.drawKey(frame, (255,255,255), (0,0,0), 0.1, fontScale=0.5)
        exitKey.drawKey(frame, (255,255,255), (0,0,0), 0.1, fontScale=0.5)
        # 控制显示/隐藏 与 退出
        if showKey.isOver(self.clickedX, self.clickedY):
            self.show = not self.show
            showKey.text = "Hide" if self.show else "Show"
            self.clickedX, self.clickedY = 0, 0
        if exitKey.isOver(self.clickedX, self.clickedY):
            exit()

    # 计算手指距离 为手部点击键盘做准备
    def calculateIntDidtance(self,pt1, pt2):
        return int(((pt1[0]-pt2[0])**2 + (pt1[1]-pt2[1])**2)**0.5)

    def run(self):
        cap = cv2.VideoCapture(0)
        # 获得视频屏幕的大小
        frameHeight, frameWidth, _ = cap.read()[1].shape
        cv2.namedWindow('video')
        # 循环处理每一帧
        while True:
            succ, img = cap.read()
            if not succ:
                break
            # 重新构建窗口大小
            frame = cv2.resize(img, (int(frameWidth*1.5), int(frameHeight*1.5)))
            # 添加鼠标移动事件监听
            cv2.setMouseCallback('video', self.getMousPos)
            # 翻转画面
            frame = cv2.flip(frame, 1)
            # 显示手部
            frame = self.tracker.findHands(frame)
            lmList = self.tracker.getPostion(frame, draw=False)
            # 获取手部各个点的位置
            if lmList:
                self.signTipX, self.signTipY = lmList[8][1], lmList[8][2]    # 食指
                self.thumbTipX, self.thumbTipY = lmList[4][1], lmList[4][2]  # 大拇指
                if self.calculateIntDidtance((self.signTipX, self.signTipY), (self.thumbTipX, self.thumbTipY)) < 50:
                    centerX = int((self.signTipX + self.thumbTipX) / 2)
                    centerY = int((self.signTipY + self.thumbTipY) / 2)
                    cv2.line(frame, (self.signTipX, self.signTipY), (self.thumbTipX, self.thumbTipY), (0,255,0), 2)
                    cv2.circle(frame, (centerX, centerY), 5, (0,255,0), cv2.FILLED)
            # 显示虚拟键盘
            self.addKeyboard(frame)
            self.controller(frame, frameWidth)
            # do not Flip the image horizontally for a selfie-view display again.
            cv2.imshow('video', frame)
            if cv2.waitKey(3) & 0xFF == 27:
                break
        # 释放资源
        cap.release()
        cv2.destroyAllWindows()

if __name__ == '__main__':
    vk = VirtualKb()
    vk.run() 